package com.xyzwps.lib.beans;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.regex.Pattern;

sealed interface PropertyMethod {

    PropertyMethod NONE = new None();

    record None() implements PropertyMethod {
    }

    record SetMethod(Class<?> beanType, Type type, Method method,
                     String propertyName) implements PropertyMethod {
    }

    record GetMethod(Class<?> beanType, Type type, Method method,
                     String propertyName) implements PropertyMethod {
    }

    static PropertyMethod decide(Class<?> beanType, Method method) {
        var modifiers = method.getModifiers();
        if (Modifier.isStatic(modifiers)) return NONE;

        var methodName = method.getName();
        if ("getClass".equals(methodName)) return NONE;

        var methodAccessLevel = AccessLevel.fromModifiers(modifiers);
        if (methodAccessLevel == AccessLevel.PRIVATE) return NONE;
        if (methodAccessLevel == AccessLevel.PROTECTED) return NONE;

        var parameterCount = method.getParameterCount();
        var returnType = method.getGenericReturnType();
        var matcher = ACCESSOR_PATTERN.matcher(methodName);
        if (matcher.matches()) {
            var accessorType = matcher.group("type");
            switch (accessorType) {
                case "set" -> {
                    if (parameterCount != 1) return NONE;
                    var type = method.getGenericParameterTypes()[0];
                    return new SetMethod(beanType, type, method, extractPropertyName(3, methodName));
                }
                case "get" -> {
                    if (parameterCount > 0) return NONE;
                    if (returnType == Void.class) return NONE;
                    var type = method.getGenericReturnType();
                    return new GetMethod(beanType, type, method, extractPropertyName(3, methodName));
                }
                case "is" -> {
                    if (parameterCount > 0) return NONE;
                    if (returnType != boolean.class) return NONE;
                    return new GetMethod(beanType, boolean.class, method, extractPropertyName(2, methodName));
                }
            }
        }
        return NONE;
    }

    Pattern ACCESSOR_PATTERN = Pattern.compile("^(?<type>(get)|(set)|(is))(?<name>[A-Z][a-zA-Z0-9]*)$");

    static String extractPropertyName(int skip, String methodName) {
        return Character.toLowerCase(methodName.charAt(skip)) + methodName.substring(skip + 1);
    }
}